#include "BMP.h"
#define out(x,y) cout << #y << ": " << x.y << endl;
using namespace std;



	BITMAPINFOHEADER BMPFile::getBMPInfoHeader(){
		return info_header;
	}
	BYTE *BMPFile::getBitmapData(){
		return bitmap_data;
	}
	DWORD *BMPFile::getColorPalette(){
		return color_palette;
	}

	void BMPFile::setBitmapData(BYTE *bitmap, int size){	
		delete bitmap_data;
		bitmap_data = new BYTE[size];				
		for (int i=0; i<size; i++){			
			bitmap_data[i] = bitmap[i];
		}
	}

	void BMPFile::createNewBITMAPFILEHEADER(int bitmapSize){
		header.bfType = WORD('B') | (WORD('M')<<8);
		header.bfSize = 14+40+4*256+bitmapSize; //14 bytes: Bitmap file header, 40 Bytes BITMAPINFOHEADER, 4*256 bytes RGB palette 
		header.bfReserved = 0;					// no value for reserved field
		header.bfOffBits = 14+40+4*256;			// offset 
	}
	void BMPFile::createNewBITMAPINFOHEADER(int width, int height){
		info_header.biSize = 40;			// size of this header
		info_header.biWidth = width;		// image width
		info_header.biHeight = height;		// image height
		info_header.biPlanes = 1;			// must be set to 1 
		info_header.biBitCount = 8;			// 8 bit per pixel
		info_header.biCompression = 0;		// no compression for BITMAP RGB file
		info_header.biSizeImage = 0;		// 0 default for BITMAP RGB file
		info_header.biXPelsPerMeter = 0;	// 0 default for BITMAP RGB file
		info_header.biYPelsPerMeter = 0;	// 0 default for BITMAP RGB file
		info_header.biClrUsed = 256;		// 2^8 colors in the color palette
		info_header.biClrImportant = 256;	// 256 important colors
	}
	void BMPFile::createNewColorPalette(){		
		color_palette = new DWORD[info_header.biClrImportant];
		for (int j=0; j<(int)info_header.biClrImportant; j++){
			color_palette[j] = DWORD(j) << 24 | DWORD(j) << 16;
			color_palette[j] = color_palette[j] | DWORD(j) << 8;
			color_palette[j] = color_palette[j] | DWORD(0);
		}
	}


	bool BMPFile::getBITMAPFILEHEADER(istream &in_stream){
		if (!get(in_stream, header.bfType)) return false;
		if (!get(in_stream, header.bfSize)) return false;
		if (!get(in_stream, header.bfReserved)) return false;
		if (!get(in_stream, header.bfOffBits)) return false;
		return true;
	}

	bool BMPFile::putBITMAPFILEHEADER(ostream &out_stream){
		if (!put(out_stream, header.bfType)) return false;
		if (!put(out_stream, header.bfSize)) return false;
		if (!put(out_stream, header.bfReserved)) return false;
		if (!put(out_stream, header.bfOffBits)) return false;
		return true;
	}


	bool BMPFile::getBITMAPINFOHEADER(istream &in_stream){
		if (!get(in_stream, info_header.biSize)) return false;
		if (!get(in_stream, info_header.biWidth)) return false;
		if (!get(in_stream, info_header.biHeight)) return false;
		if (!get(in_stream, info_header.biPlanes)) return false;
		if (!get(in_stream, info_header.biBitCount)) return false;
		if (!get(in_stream, info_header.biCompression)) return false;
		if (!get(in_stream, info_header.biSizeImage)) return false;
		if (!get(in_stream, info_header.biXPelsPerMeter)) return false;
		if (!get(in_stream, info_header.biYPelsPerMeter)) return false;
		if (!get(in_stream, info_header.biClrUsed)) return false;
		if (!get(in_stream, info_header.biClrImportant)) return false;
		return true;
	}

	bool BMPFile::putBITMAPINFOHEADER(ostream &out_stream){
		if (!put(out_stream, info_header.biSize)) return false;
		if (!put(out_stream, info_header.biWidth)) return false;
		if (!put(out_stream, info_header.biHeight)) return false;
		if (!put(out_stream, info_header.biPlanes)) return false;
		if (!put(out_stream, info_header.biBitCount)) return false;
		if (!put(out_stream, info_header.biCompression)) return false;
		if (!put(out_stream, info_header.biSizeImage)) return false;
		if (!put(out_stream, info_header.biXPelsPerMeter)) return false;
		if (!put(out_stream, info_header.biYPelsPerMeter)) return false;
		if (!put(out_stream, info_header.biClrUsed)) return false;
		if (!put(out_stream, info_header.biClrImportant)) return false;
		return true;
	}


	bool BMPFile::getColorPalette(istream &in_stream){
		color_palette = new DWORD[info_header.biClrImportant];
		for (int j=0; j < (int)info_header.biClrImportant; j++){
			BYTE red, green, blue, unused;
			int i = in_stream.get();
			if (in_stream.eof())
				return false;
			blue = (BYTE)i;
			i = in_stream.get();
			if (in_stream.eof())
				return false;
			green = (BYTE)i;
			i = in_stream.get();
			if (in_stream.eof())
				return false;
			red = (BYTE)i;
			i = in_stream.get();
			if (in_stream.eof())
				return false;
			unused = (BYTE)i;
			color_palette[j] = DWORD(red) << 24 | DWORD(green) << 16;
			color_palette[j] = color_palette[j] | DWORD(blue) << 8;
			color_palette[j] = color_palette[j] | DWORD(unused);			
		}
		return true;
	}


	bool BMPFile::putColorPalette(ostream &out_stream){
		DWORD current_color;
		for (int j=0; j < (int)info_header.biClrImportant; j++){
			current_color = color_palette[j];
			BYTE color_unused = (BYTE)current_color;
			BYTE color_blue = current_color >> 8;
			BYTE color_green = current_color >> 16;
			BYTE color_red = current_color >> 24;
			out_stream.put(color_blue);			//color blue
			if (!out_stream.good())
				return false;
			out_stream.put(color_green);		//color green
			if (!out_stream.good())
				return false;
			out_stream.put(color_red);			//color red
			if (!out_stream.good())
				return false;
			out_stream.put(color_unused);		//color unused		
			if (!out_stream.good())
				return false;
		}
		return true;
	}

	bool BMPFile::getBitmapData(istream &in_stream){
		bitmap_data = new BYTE[info_header.biWidth*info_header.biHeight];
		int pos = 0;
		unsigned long start = (int)header.bfOffBits+(int)info_header.biWidth*(int)info_header.biHeight-(int)info_header.biWidth;
		int current_BYTE;
		int num_to_seek = start;
		in_stream.seekg(start, ios_base::beg);
		if (!in_stream.good())
			return false;		
		for (int i = 0; i<info_header.biHeight; i++){
			for (int j = 0; j<info_header.biWidth; j++){
				current_BYTE = in_stream.get();
				if (in_stream.eof())
					return false;				
				bitmap_data[pos] = (BYTE)(current_BYTE);
				pos++;
				if (i == 0)
					cout << (int)bitmap_data[pos-1] << " ";				
			}			
			num_to_seek = num_to_seek - info_header.biWidth;
			in_stream.seekg(num_to_seek, ios_base::beg);			
			//if (!in_stream.good())
			//	return false;
		}
		return true;
	}



	bool BMPFile::putBitmapData(ostream &out_stream){				
		int pos = 0;
		unsigned long start = (int)info_header.biWidth*(int)info_header.biHeight-(int)info_header.biWidth;		
		pos = start;
		for (int i = 0; i<info_header.biHeight; i++){
			for (int j = 0; j<info_header.biWidth; j++){				
				out_stream.put(bitmap_data[pos]);
				if (!out_stream.good())
					return false;
				pos++;
			}		
			start = start - (int)info_header.biWidth;
			pos = start;
			
			if ((info_header.biWidth%4) != 0){							// Bytes di padding per avere un multiplo di 4 byte
				int bytePadding = 4 - (info_header.biWidth%4);				
				for (int pad=0; pad<bytePadding; pad++)
					out_stream.put(NULL);							

			}

			if (!out_stream.good())
				return false;
		}
		return true;
	}

	void BMPFile::getPixel(int x, int y){
		x--; y--;
		int pos = x*info_header.biWidth+y;
		cout << "pixel(" << x+1 << ", " << y+1 << "): " << (int)bitmap_data[pos];
	}
	void BMPFile::printBITMAPFILEHEADER(){
		cout << "BITMAPFILEHEADER: " << endl;
		cout << "Type ('BM' to be read in little endian): " << hex << header.bfType << endl;
		cout << "Size: " << dec << header.bfSize << " bytes" << endl;
		cout << "Offbits: " << dec << header.bfOffBits << endl;
		cout << "Reserved: " << dec << header.bfReserved << endl << endl;
	}
	void BMPFile::printBITMAPINFOHEADER(){
		cout << "BITMAPINFOHEADER: " << endl;
		out(info_header,biSize);
		out(info_header,biWidth);
		out(info_header,biHeight);
		out(info_header,biPlanes);
		out(info_header,biBitCount);
		out(info_header,biCompression);
		out(info_header,biXPelsPerMeter);
		out(info_header,biYPelsPerMeter);
		out(info_header,biClrUsed);
		out(info_header,biClrImportant);
		cout << endl;
	}
	void BMPFile::printColorPalette(){

		cout << endl<<"COLOR PALETTE (RGBx)" << endl;

		for (int i=0; i<(int)info_header.biClrImportant; i++){
			cout << dec << "Color Palette [" << i << "]: " << "#" << setw(8) << setfill('0') << hex << uppercase << (int)color_palette[i] << endl;
			if (i > 0 && i%10==0)
				break;
		}
		cout << dec << "..." << endl;	
		cout << dec << "Color Palette [" << (info_header.biClrImportant - 1) << "]: " << "#" << setw(8) << setfill('0') << hex << uppercase << (int)color_palette[(info_header.biClrImportant - 1)] << endl;
		cout << dec << endl;
	}
	
	void BMPFile::printBitmapData(){
		cout << "First element of bitmap is: " << (int)bitmap_data[0] << endl;
		int pos = info_header.biWidth*info_header.biHeight-1;
		cout << "Last element of bitmap is: " << (int)bitmap_data[pos] << endl;
		//cout << "Bitmap sample (40 first elements): " << endl;

		//for (int i = header.bfOffBits; i < info_header.biWidth * info_header.biHeight ; i++){
		//	cout <<  (int)bitmap_data[i] << " ";
		//	if ((i+1)%40 == 0) break;
		//}
	}


	bool BMPFile::getBMP_Headers_ColorPalette_BitmapData(const char *fileName){
		fstream in(fileName, ios::in|ios::binary);		//open bmp file to read at binary format
		if (!in){
			cout << "Cannot open the file: " << fileName << endl;
			return false;
		}		
		if (!getBITMAPFILEHEADER(in))
			return false;
		printBITMAPFILEHEADER();

		if (!getBITMAPINFOHEADER(in))
			return false;
		printBITMAPINFOHEADER();
		
		if (!getColorPalette(in))
			return false;
		printColorPalette();
		
		if (!getBitmapData(in))
			return false;
		
		getPixel(100,100);
		cout << endl;
		printBitmapData();		
		cout << endl;
		in.close();

		return true;
	}

	bool BMPFile::buildBMPFile(ostream &out, int ImageWidth, int ImageHeight, ColorPaletteEntry *_ColorMap, BYTE *ImageIndices){
		
		int imageSize = ImageWidth * ImageHeight;		

		createNewBITMAPFILEHEADER(imageSize);		
		putBITMAPFILEHEADER(out);
		
		createNewBITMAPINFOHEADER(ImageWidth, ImageHeight);		
		putBITMAPINFOHEADER(out);

		delete color_palette;
		color_palette = new DWORD[info_header.biClrImportant];		
		for (int j=0; j<(int)info_header.biClrImportant; j++){
			color_palette[j] = DWORD(0);
			color_palette[j] = DWORD(_ColorMap[j].rgbRed) << 24 | DWORD(_ColorMap[j].rgbGreen) << 16;
			color_palette[j] = color_palette[j] | DWORD(_ColorMap[j].rgbBlue) << 8;
			color_palette[j] = color_palette[j] | DWORD(0);							
		}
		putColorPalette(out);
				
		delete bitmap_data;
		bitmap_data = new BYTE[imageSize];				
		for (int i=0; i<imageSize; i++){			
			bitmap_data[i] = ImageIndices[i];									
		}										
		putBitmapData(out);		

		return true;
	}
